package com.hmdp.service.impl;

import cn.hutool.core.lang.UUID;
import com.hmdp.service.ILock;
import jakarta.annotation.Resource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**分布式锁的简单实现
 * 利用redis的setnx方法存入一个key&value，这个特殊的方法检查这个key是否存在，
 * 如果不存在就存入这个key，存入成功就返回true，存入失败就返回false
 * ，如果存入失败就说明锁被占用，此时就不能再存入，就返回false
 * 利用setnx方法进行加锁，同时增加过期时间
 * ，防止死锁，此方法可以保证加锁和增加过期时间具有原子性
 */
public class SimpleRedisLock implements ILock {

    private String name;  //调用者可以指定锁的名称


    private StringRedisTemplate stringRedisTemplate;  //redis操作对象，等待尝试获取锁的哪个线程注入，保证线程安全
    public SimpleRedisLock(String s, StringRedisTemplate stringRedisTemplate) {
    }


    private static final String KEY_PREFIX = "lock:";//常量：锁的前缀（即key）

    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";//常量：线程id的前缀（即value）

    //redis加载lua脚本，设置为静态变量，保证只加载一次，避免重复加载
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); //加载lua脚本
        UNLOCK_SCRIPT.setResultType(Long.class);
    }





    //获取锁，其实就是利用redis的setnx方法存入一个key&value，这个特殊的方法检查这个key是否存在，
    // 如果不存在就存入这个key，存入成功就返回true，存入失败就返回false
    @Override
    public boolean tryLock(long timeoutSec) {
        // 获取当前访问的线程id
        String threadId = String.valueOf(Thread.currentThread().getId());
        //拼接一个字符串，作为每个线程的标识
        String KEY_PREFIX = ID_PREFIX + threadId;

        // 利用setnx方法进行加锁，同时增加过期时间，存入成功就返回true（代表获取到锁），存入失败就返回false（代表未获取到锁）
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name,  KEY_PREFIX, timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
    }

    /**
     * 优化：防止错删锁
     * 初级思路：删除所之前判断一下申请锁是存入的（value--即线程id）是否是自己的，是自己的就删除，不是就不删除
     */
    //释放锁，其实就是删除这个key，删除成功（代表释放锁成功），此后其他线程就可以获取到锁
    @Override
    public void unlock() {
//        // 获取线程标示
//        String threadId = ID_PREFIX + Thread.currentThread().getId();
//        // 获取锁中的标示(即redis中存的key&value中的value)
//        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//        // 判断标示是否一致
//        if(threadId.equals(id)) {
//            // 释放锁
//            stringRedisTemplate.delete(KEY_PREFIX + name);
//            /**
//             * 删除时的逻辑是根据key来删除，但是对于多个线程来说key是一样的啊，而value因为添加了UUUID和线程自身ID值因此不一样，所以删除时会出现原子性问题
//             * 考虑用lua脚本来实现
//             */
//        }
        // rides执行lua脚本保证原子性---lua脚本名称，参数集合1，参数2
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name), //集合传参，里面放的是key
                ID_PREFIX + Thread.currentThread().getId());  //传参2，是value，脚本中需要判断一下是否是当前线程自己的自己的线程id
    }
}
